from django.views.decorators.cache import cache_page
from django.core.cache import cache
from django.core.cache import caches
from operator import itemgetter
from collections import defaultdict
import pandas as pd
import random
import math
from base.models.travelSpotLDA import travelSpotLDA
from base.models.travelSpot import travelSport
from django.http import JsonResponse
import numpy as np


# 对于游客用户 --- 进行lda主题的粗略推荐

from base.views.modelinfo import getTidInfo
from base.serializers import travelSportSerializer


def guest_lda_recommand(request):
    places = {}
    # user_rec = [4, 4, 4]
    requestList = request.GET['rank'].split(',')
    user_rec = []
    for x in requestList:
        user_rec.append(int(x) * 10)

    # 构造景点列表
    p = travelSpotLDA.objects.all()
    for item in p:
        rankList = []
        rankList.append(item.score_culture)
        rankList.append(item.score_nature)
        rankList.append(item.score_food)

        places[item.spot.tid] = rankList

    # 71519336: [0.7070250957214373, 0.139659626473506, 0.1533152778050565]} ...

    finall = []
    for place, features in places.items():
        finall.append((place, np.dot(user_rec, features)))

    res = reversed(sorted(finall, key=lambda x: x[1], reverse=True)[:100])
    # return JsonResponse(res, safe=False)

    spotList = []
    for item in res:
        print(item)
        spot = travelSport.objects.get(tid=item[0])
        spotList.append(spot)

    formated_res = travelSportSerializer(spotList, many=True).data
    return JsonResponse(formated_res, safe=False)


# 基于用户的协同过滤推荐
# from Utils import modelsave


def LoadMovieLensData(filepath, train_rate):
    # ratings = pd.read_table(filepath, sep="::", header=None, names=["UserID", "MovieID", "Rating", "TimeStamp"],\
    #                         engine='python')
    # ratings = ratings[['UserID','MovieID']]

    train = []
    test = []
    random.seed(3)
    ratings = pd.read_csv(filepath)
    for idx, row in ratings.iterrows():

        user = int(row['userId'])
        item = int(row['tid'])

        if random.random() < train_rate:
            train.append([user, item])
        else:
            test.append([user, item])
    return PreProcessData(train), PreProcessData(test)


def PreProcessData(originData):
    """
    建立User-Item表，结构如下：
        {"User1": {MovieID1, MoveID2, MoveID3,...}
         "User2": {MovieID12, MoveID5, MoveID8,...}
         ...
        }
    """
    trainData = dict()
    for user, item in originData:
        trainData.setdefault(user, set())
        trainData[user].add(item)
    return trainData


def user_cf(request):
    rqtotal = int(request.GET['total'])

    class UserCF(object):
        """ User based Collaborative Filtering Algorithm Implementation"""

        def __init__(self, trainData, similarity="cosine"):
            self._trainData = trainData
            self._similarity = similarity
            self._userSimMatrix = dict()  # 用户相似度矩阵
            self._recommends = dict()

        def similarity(self):
            if len(self._userSimMatrix) > 0:
                print(f"len:{self._userSimMatrix}")
                return

            # 建立User-Item倒排表
            item_user = dict()
            for user, items in self._trainData.items():
                for item in items:
                    item_user.setdefault(item, set())
                    item_user[item].add(user)

            # 建立用户物品交集矩阵W, 其中C[u][v]代表的含义是用户u和用户v之间共同喜欢的物品数
            for item, users in item_user.items():
                for u in users:
                    for v in users:
                        if u == v:
                            continue
                        self._userSimMatrix.setdefault(u, defaultdict(int))
                        if self._similarity == "cosine":
                            # 将用户u和用户v共同喜欢的物品数量加一
                            self._userSimMatrix[u][v] += 1
                        elif self._similarity == "iif":
                            self._userSimMatrix[u][v] += 1. / \
                                math.log(1 + len(users))

            # 建立用户相似度矩阵
            for u, related_user in self._userSimMatrix.items():
                # 相似度公式为 |N[u]∩N[v]|/sqrt(N[u]||N[v])
                for v, cuv in related_user.items():
                    nu = len(self._trainData[u])
                    nv = len(self._trainData[v])
                    self._userSimMatrix[u][v] = cuv / math.sqrt(nu * nv)

            backup = self._userSimMatrix
            # print(backup)
            print("==== 成功建立相似度矩阵 ====")   # 超级浪费时间
            cache.set('backup', backup)
            hh = cache.get('backup', 3600)
            if hh is None:
                print("无缓存")

        def recommend(self, user, N, K):
            """
            用户u对物品i的感兴趣程度：
                p(u,i) = ∑WuvRvi
                其中Wuv代表的是u和v之间的相似度， Rvi代表的是用户v对物品i的感兴趣程度，因为采用单一行为的隐反馈数据，所以Rvi=1。
                所以这个表达式的含义是，要计算用户u对物品i的感兴趣程度，则要找到与用户u最相似的K个用户，对于这k个用户喜欢的物品且用户u
                没有反馈的物品，都累加用户u与用户v之间的相似度。
            :param user: 被推荐的用户user
            :param N: 推荐的商品个数
            :param K: 查找的最相似的用户个数
            :return: 按照user对推荐物品的感兴趣程度排序的N个商品
            """
            recommends = dict()
            # 先获取user具有正反馈的item数组
            related_items = self._trainData[user]
            # 将其他用户与user按照相似度逆序排序之后取前K个
            for v, sim in sorted(self._userSimMatrix[user].items(), key=itemgetter(1), reverse=True)[:K]:
                # 从与user相似的用户的喜爱列表中寻找可能的物品进行推荐
                for item in self._trainData[v]:
                    # 如果与user相似的用户喜爱的物品与user喜欢的物品重复了，直接跳过
                    if item in related_items:
                        continue
                    recommends.setdefault(item, 0.)
                    recommends[item] += sim
            # 根据被推荐物品的相似度逆序排列，然后推荐前N个物品给到用户
            return dict(sorted(recommends.items(), key=itemgetter(1), reverse=True)[:N])

        def train(self):
            self.similarity()

    tmp = cache.get('backup')
    if tmp:
        # 有缓存直接返回
        UserCF._userSimMatrix = tmp
        print(len(UserCF._trainData[3]))
        return JsonResponse(len(tmp), safe=False)

    train, test = LoadMovieLensData(
        'C:\\Users\\17914\\Desktop\\travelCF\\base\\views\\comment_data.csv', 0.8)
    print("train data size: %d, test data size: %d" %
          (len(train), len(test)))

    UserCF = UserCF(train)
    print(len(UserCF._recommends))
    if len(UserCF._recommends) < 10:
        print("重新计算矩阵")
        UserCF.train()

        dist = []
        n = len(list(test.keys()))

        for i in range(n):
            try:
                tmp = UserCF.recommend(list(test.keys())[0], rqtotal, 380)
                dist.append(tmp)
            except:
                print("Error")

        return JsonResponse(dist, safe=False)   # 计算结果保存到tmp.txt
